using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Xml;
using System.Xml.Schema;
using System.Xml.XPath;
using System.Security.Policy;
using Fuzzware.Common;
using Fuzzware.Common.XML;

namespace Fuzzware.ConvertFromXML.Processors
{
    public class XMLProcInstCommands
    {
        static String FuzzNS;
        static XmlSchemaSet FuzzSchemaSet;
        static List<XmlSchemaAttribute> ProcInstAtts;

        public static void ExcludeNodes(ref XPathNavigator[] Nodes)
        {
            List<XPathNavigator> LNodes = new List<XPathNavigator>();
            // Get XPathNavs to all the command attributes
            for (int i = 0; i < Nodes.Length; i++)
            {
                bool dontFuzz = false;
                XPathNavigator Node = Nodes[i].Clone();
                SearchForDontFuzzProcInst(Node, out dontFuzz);

                if (!dontFuzz)
                {
                    LNodes.Add(Nodes[i]);
                }
            }
            Nodes = LNodes.ToArray();
        }

        public static void ExcludeNodes(XPathObjectList Nodes)
        {
            // Get XPathNavs to all the command attributes
            for (int i = 0; i < Nodes.Count; i++)
            {
                bool dontFuzz = false;
                XPathNavigator Node = Nodes[i].Clone();
                SearchForDontFuzzProcInst(Node, out dontFuzz);

                if (dontFuzz)
                {
                    Nodes.Remove(i);
                    i--;
                }
            }
        }

        private static void SearchForDontFuzzProcInst(XPathNavigator Node, out bool Value)
        {
            // Move to the sibling node before the current node.  Since you can stack many processing instructions one after the 
            // next for a particular node, if we find a PI continue searching for more, until we hit something that isn't a PI.
            while (Node.MoveToPrevious())
            {
                // Check to see if it is a processing instruction
                if (((IHasXmlNode)Node).GetNode() is XmlProcessingInstruction)
                {
                    XmlProcessingInstruction PI = ((IHasXmlNode)Node).GetNode() as XmlProcessingInstruction;
                    // Check to see if it is a PI for our app
                    if ((PI.LocalName == XMLHelper.AppName()) || (PI.LocalName == "Schemer"))
                    {
                        // Check to see if it has a valid command
                        String PIname;
                        String PIvalue;
                        if (XMLProcInstCommands.CheckCommandExists(PI.Data, out PIname, out PIvalue))
                        {
                            if (PIname == "dontFuzz")
                            {
                                // If we find the explicit instruction, stop searching
                                if (PIvalue == "true")
                                {
                                    Value = true;
                                    return;
                                }
                                Value = false;
                                return;
                            }
                        }
                    }
                }
                else
                {
                    break;
                }
            }
                
            // If we get to here search parents.
            if(!Node.MoveToParent())
            {
                // We must be at the root node, and we haven't found a command to not fuzz, so fuzz away!
                Value = false;
                return;
            }
            XMLProcInstCommands.SearchForDontFuzzProcInst(Node, out Value);
            return;
        }

        private static void LoadAndCompileSchema()
        {
            // Get the fuzzer commands schema, and validate it.
            FuzzSchemaSet = XMLHelper.LoadAndCompileSchema("XMLProcInstCommands.xsd", Assembly.GetExecutingAssembly(), null, out FuzzNS);
        }

        /// <summary>
        /// Checks that the processing instruction exists in the schema listing valid fuzzer commands.
        /// </summary>
        /// <param name="PIData">Expects a string of the form 'command="value"'</param>
        /// <returns></returns>
        public static bool CheckCommandExists(String PIData, out String Command, out String Value)
        {
            if (null == FuzzSchemaSet)
                LoadAndCompileSchema();

            Command = "";
            Value = "";

            int equalsIndex = PIData.IndexOf('=');
            if (-1 == equalsIndex)
            {
                Log.Write(MethodBase.GetCurrentMethod(), "Malformed Processing Instruction - '" + PIData + "'", Log.LogType.Warning);
                return false;
            }

            Command = PIData.Substring(0, equalsIndex);
            Value = PIData.Substring(equalsIndex + 2, PIData.Length - equalsIndex - 3);

            // Handle the special PI
            if (Command.Equals("TargetNode"))
                return true;

            XmlQualifiedName AttName = new XmlQualifiedName(Command, FuzzNS);

            if (!FuzzSchemaSet.GlobalAttributes.Contains(AttName))
            {
                Log.Write(MethodBase.GetCurrentMethod(), "Ignoring command '" + PIData + "'.  It is not valid for this application", Log.LogType.Info);
                return false;
            }

            // Should really verify that the value is correct, this will probably require creating XML and validating it,
            // unless there is someother way of verifying?
            return true;
        }

        /// <summary>
        /// Return the value of a command from a processing instruction string
        /// </summary>
        /// <param name="PIData">The processing instruction data containing lots of command="va;ue" strings</param>
        /// <param name="Command">The command to get the value of</param>
        /// <param name="Value">The value of the command that is returned</param>
        /// <returns></returns>
        public static bool GetCommandValue(String PIData, String Command, out String Value)
        {
            Value = "";
            String CommandFmt = Command + "=\"";
            int offset = PIData.IndexOf(CommandFmt);
            if (-1 == offset)
                return false;

            int endquote = PIData.IndexOf("\"", offset + CommandFmt.Length);
            if (-1 == offset)
                return false;

            Value = PIData.Substring(offset + CommandFmt.Length, endquote - (offset + CommandFmt.Length));
            return true;
        }

        public static void GetMultipleCommandValues(String PIData, String Command, out List<String> Values)
        {
            Values = new List<string>();
            String CommandFmt = Command + "=\"";
            int offset = 0;

            while (-1 != (offset = PIData.IndexOf(CommandFmt, offset)))
            {
                int endquote = PIData.IndexOf("\"", offset + CommandFmt.Length);
                if (-1 == offset)
                    return;

                Values.Add(PIData.Substring(offset + CommandFmt.Length, endquote - (offset + CommandFmt.Length)));
                offset = endquote;
            }
        }

        /// <summary>
        /// Return a list of all the pre-defined XML Processing Instructions.
        /// </summary>
        /// <returns></returns>
        public static List<XmlSchemaAttribute> GetProcInstCommands()
        {
            if (null == FuzzSchemaSet)
                LoadAndCompileSchema();

            if (null == ProcInstAtts)
            {
                ProcInstAtts = new List<XmlSchemaAttribute>();

                foreach (XmlSchemaAttribute Att in FuzzSchemaSet.GlobalAttributes.Values)
                {
                    ProcInstAtts.Add(Att);
                }
            }
            return ProcInstAtts;
        }
    }
}
